package it.sd.utility;

import java.io.InputStream;
import java.io.StringWriter;
import java.util.LinkedList;
import java.util.StringTokenizer;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.CDATASection;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;

public class XMLWrapper {
	
//	private static final String ROOT = "root";
	
	private Document document = null;
	
	private DocumentBuilder getBuilder() throws Exception {
		DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
		domFactory.setNamespaceAware(true); 
		return domFactory.newDocumentBuilder();
	}
	
	public XMLWrapper(Document doc) {
		this.document = doc;
	}
	
	public XMLWrapper(InputStream is) throws Exception  {
		document = getBuilder().parse(is);
		is.close();
	}	

	public String toString() {
		
		try {
			DOMSource source = new DOMSource(document);
			StringWriter stringWriter = new StringWriter();
			StreamResult result = new StreamResult(stringWriter);
			Transformer transformer = TransformerFactory.newInstance().newTransformer();
			transformer.setOutputProperty(OutputKeys.INDENT, "yes");
			transformer.setOutputProperty(OutputKeys.ENCODING, "utf-8");
			transformer.transform(source, result);
			return stringWriter.getBuffer().toString();	
		} catch(Exception e) {
			e.printStackTrace();
			return "errore documento xml";
		}
	}
	
	public Element getRootElement() {
		return this.document.getDocumentElement();
	}

	public Element setValue(String name, String value) {
		Element root = document.getDocumentElement();
		Element figlio = getFiglio(root, name);
		figlio.setTextContent(value);
		return figlio;
	}
	
	public Element addValue(String name, String value) {
		Element root = document.getDocumentElement();
		Element figlio = document.createElement(name);
		figlio.setTextContent(value);
		root.appendChild(figlio);
		return figlio;
	}
	
	public Element getValue(String name) {
		Element root = document.getDocumentElement();
		Element figlio = getFiglio(root, name);
		return figlio;
	}

	public String extractStringValue(String stringPath) throws Exception {
		return (String)extractValue(stringPath, XPathConstants.STRING);
	}

	public static String extractStringValue(Element e, String stringPath) throws Exception {
		return (String)extractValue(e, stringPath, XPathConstants.STRING);
	}

	public boolean extractBooleanValue(String stringPath) throws Exception {
		return (Boolean)extractValue(stringPath, XPathConstants.BOOLEAN);
	}

	public NodeList extractNodeValue(String stringPath) throws Exception {
		return (NodeList)extractValue(stringPath, XPathConstants.NODESET);
	}

	public Double extractNodeNumber(String stringPath) throws Exception {
		return (Double)extractValue(stringPath, XPathConstants.NUMBER);
	}

	private static Object extractValue(Element e, String stringPath, QName qname) throws Exception {
		
		if (stringPath.startsWith("${")){
			stringPath = stringPath.substring(2, stringPath.length()-1);
		}
		
		XPath xpath = XPathFactory.newInstance().newXPath();
		XPathExpression expression = xpath.compile(stringPath);
		Object result = expression.evaluate(e, qname);
		return result;
	}

	
	private Object extractValue(String stringPath, QName qname) throws Exception {
		
		if (stringPath.startsWith("${")){
			stringPath = stringPath.substring(2, stringPath.length()-1);
		}
		
		XPath xpath = XPathFactory.newInstance().newXPath();
		XPathExpression expression = xpath.compile(stringPath);
		Object result = expression.evaluate(document, qname);
		return result;
	}
	
	public static Element getFiglio(Element root, String tagName) {
		Element figlio = null;
		
		NodeList nodelist = root.getChildNodes();
		for (int i = 0; i < nodelist.getLength(); i++) {
			Node node = nodelist.item(i);
			if (node instanceof Element) {
				Element x = (Element) node;
				if (x.getNodeName().equals(tagName)) {
					figlio = (Element)node;
					break;
				}
			}
			
		}
		
		return figlio;
	}

	public static String getFiglioContent(Element root, String tagName) {
		Element figlio = getFiglio(root, tagName);
		if (figlio!=null) {
			return figlio.getTextContent();
		} else {
			return null;
		}
	}
	
	public void addDoc(XMLWrapper d) {
		Document toAdd = d.document;

		addElement(document.getDocumentElement(), toAdd.getDocumentElement());
		
	}

	public void addDoc(Element e) {

		addElement(document.getDocumentElement(), e);
		
	}
	private void addElement(Element elementDest, Element elementSource) {
		
		Element elementNew = document.createElement(elementSource.getTagName());
//		elementNew.setTextContent(elementSource.getTextContent());
		elementDest.appendChild(elementNew);

		NamedNodeMap attrs = elementSource.getAttributes();
		for (int i = 0; i<attrs.getLength(); i++) {
			Node n = attrs.item(i);
			elementNew.setAttribute(n.getLocalName(), n.getNodeValue());
		}
		
		NodeList lista = elementSource.getChildNodes();
		for (int i = 0; i<lista.getLength(); i++) {
			Node nodo = lista.item(i);
			if (nodo instanceof Element) {
				addElement(elementNew, (Element)nodo);
			} else if (nodo instanceof Text) {
				Text testo = document.createTextNode(((Text)nodo).getNodeValue());
				elementNew.appendChild(testo);
			}
		}
		
	}
	
	public Element addPathNodo(String nome) {
		Element element = document.getDocumentElement();
		return addPathNodo(element, nome);
	}

	public Element addPathNodo(Element element, String nome) {
		
		LinkedList<String> path = parseNome(nome);
		while (path.size()>0) {
			String nodo = path.removeFirst();
			boolean last = (path.size()==0);
			element = getOrCreate(element, nodo, last);
		}
		
		return element;
	}

	public Element addPathNodo(String nome, String value) {
		Element element = addPathNodo(nome);
		element.setTextContent(value);
		return element;
	}

	public Element addPathCdata(String nome, String value) {
		Element element = addPathNodo(nome);
		CDATASection s = element.getOwnerDocument().createCDATASection(value);
		element.appendChild(s);
		return element;
	}

	public Element addPathCdata(Element padre, String nome, String value) {
		Element element = padre.getOwnerDocument().createElement(nome);
		padre.appendChild(element);
		CDATASection s = element.getOwnerDocument().createCDATASection(value);
		element.appendChild(s);
		return element;
	}

	private Element getOrCreate(Element padre, String nodo, boolean last) {
		Element element = null;
	
		
		if (padre==null) { 
			element = document.createElement(nodo);
			document.appendChild(element);
		} else {
			if (!last) {
				element = getElementByName(padre, nodo);
			}
			if (element==null) {
				element = document.createElement(nodo);
				padre.appendChild(element);
			}
		}
		
		return element;
	}

	private LinkedList<String> parseNome(String nome) {
		LinkedList<String> path = new LinkedList<String>();
		
		StringTokenizer st = new StringTokenizer(nome, ".");
		while (st.hasMoreTokens()) {
			path.add(st.nextToken());
		}
		
		return path;
	}
	
	private Element getElementByName(Element padre, String nome) {
		
		Element figlio = null;
		
		NodeList lista = padre.getChildNodes();
		for (int i = 0; i < lista.getLength(); i++) {
			Node node = lista.item(i);
			if (node instanceof Element) {
				Element x = (Element)node;
				if (nome.equals(x.getNodeName())) {
					figlio = x;
					break;
				}
			}
		}
		
		return figlio;
	}
}
